/*global jQuery, console, define, setTimeout, window*/
(function () {
  'use strict';
  let definePinchZoom = function ($) {

    /**
         * Pinch zoom using jQuery
         * @version 0.0.2
         * @author Manuel Stofer <mst@rtp.ch>
         * @param el
         * @param options
         * @constructor
         */
    let PinchZoom = function (el, options) {
      this.el = $(el);
      this.zoomFactor = 1;
      this.lastScale = 1;
      this.offset = {
        x: 0,
        y: 0,
      };
      this.options = $.extend({}, this.defaults, options);
      this.setupMarkup();
      this.bindEvents();
      this.update();
      // default enable.
      this.enable();

    };
    let sum = function (a, b) {
      return a + b;
    };
    let isCloseTo = function (value, expected) {
      return value > expected - 0.01 && value < expected + 0.01;
    };

    PinchZoom.prototype = {

      defaults: {
        tapZoomFactor: 2,
        zoomOutFactor: 1.1,
        animationDuration: 300,
        maxZoom: 4,
        minZoom: 0.5,
        lockDragAxis: false,
        use2d: true,
        zoomStartEventName: 'pz_zoomstart',
        zoomEndEventName: 'pz_zoomend',
        dragStartEventName: 'pz_dragstart',
        dragEndEventName: 'pz_dragend',
        doubleTapEventName: 'pz_doubletap',
      },

      /**
             * Event handler for 'dragstart'
             * @param event
             */
      handleDragStart: function (event) {
        this.el.trigger(this.options.dragStartEventName);
        this.stopAnimation();
        this.lastDragPosition = false;
        this.hasInteraction = true;
        this.handleDrag(event);
      },

      /**
             * Event handler for 'drag'
             * @param event
             */
      handleDrag: function (event) {
        if (this.zoomFactor > 1.0) {
          let touch = this.getTouches(event)[0];
          this.drag(touch, this.lastDragPosition);
          // this.offset = this.sanitizeOffset(this.offset);
          this.lastDragPosition = touch;
        }
      },

      handleDragEnd: function () {
        this.el.trigger(this.options.dragEndEventName);
        this.end();
      },

      /**
             * Event handler for 'zoomstart'
             * @param event
             */
      handleZoomStart: function (event) {
        this.el.trigger(this.options.zoomStartEventName);
        this.stopAnimation();
        this.lastScale = 1;
        this.nthZoom = 0;
        this.lastZoomCenter = false;
        this.hasInteraction = true;
      },

      /**
             * 缩放的事件处理程序
             * @param event
             */
      handleZoom: function (event, newScale) {
        // a relative scale factor is used
        let touchCenter = this.getTouchCenter(this.getTouches(event));
        let scale = newScale / this.lastScale;
        this.lastScale = newScale;

        // 第一次触摸事件由于不精确而被丢弃
        this.nthZoom += 1;
        if (this.nthZoom > 3) {
          this.scale(scale, touchCenter);
          this.drag(touchCenter, this.lastZoomCenter);
        }
        this.lastZoomCenter = touchCenter;
      },

      handleZoomEnd: function () {
        this.el.trigger(this.options.zoomEndEventName);
        this.end();
      },

      /**
             * Event handler for 'doubletap'
             * @param event
             */
      handleDoubleTap: function (event) {
        let center = this.getTouches(event)[0];
        let zoomFactor = this.zoomFactor > 1 ? 1 : this.options.tapZoomFactor;
        let startZoomFactor = this.zoomFactor;
        let updateProgress = (function (progress) {
          this.scaleTo(startZoomFactor + progress * (zoomFactor - startZoomFactor), center);
        }).bind(this);

        if (this.hasInteraction) {
          return;
        }
        if (startZoomFactor > zoomFactor) {
          center = this.getCurrentZoomCenter();
        }

        this.animate(this.options.animationDuration, updateProgress, this.swing);
        this.el.trigger(this.options.doubleTapEventName);
      },

      /**
             * 偏移的最大值/最小值
             * @param offset
             * @return {Object} the sanitized offset
             */
      sanitizeOffset: function (offset) {
        let maxX = (this.zoomFactor - 1) * this.getContainerX();
        let maxY = (this.zoomFactor - 1) * this.getContainerY();
        let maxOffsetX = Math.max(maxX, 0);
        let maxOffsetY = Math.max(maxY, 0);
        let minOffsetX = Math.min(maxX, 0);
        let minOffsetY = Math.min(maxY, 0);
        let RY = offset.y
                ;

        if(offset.y < 0 ){
          RY = Math.min(Math.max(offset.y, minOffsetY), maxOffsetY);
        }
        else if(offset.y > this.getContainerY()){
          RY = this.getContainerY();
        }

        console.log(
          'offset:',offset,'\n',
          'maxX:',maxX,'\n',
          'maxY:',maxY,'\n',
          '容器高度：',this.getContainerY(),'\n'
        );

        return {
          x: Math.min(Math.max(offset.x, minOffsetX), maxOffsetX),
          y: RY,
        };
      },

      /**
             * Scale to a specific zoom factor (not relative)
             * @param zoomFactor
             * @param center
             */
      scaleTo: function (zoomFactor, center) {
        this.scale(zoomFactor / this.zoomFactor, center);
      },

      /**
             * 从指定的中心缩放元素
             * @param scale
             * @param center
             */
      scale: function (scale, center) {
        scale = this.scaleZoomFactor(scale);
        this.addOffset({
          x: (scale - 1) * (center.x + this.offset.x),
          y: (scale - 1) * (center.y + this.offset.y),
        });
      },

      /**
             * 相对于当前状态缩放缩放系数
             * @param scale
             * @return the actual scale (can differ because of max min zoom factor)
             */
      scaleZoomFactor: function (scale) {
        let originalZoomFactor = this.zoomFactor;
        this.zoomFactor *= scale;
        this.zoomFactor = Math.min(this.options.maxZoom, Math.max(this.zoomFactor, this.options.minZoom));
        return this.zoomFactor / originalZoomFactor;
      },

      /**
             * 拖动元素
             * @param center
             * @param lastCenter
             */
      drag: function (center, lastCenter) {
        // console.log('拖动事件参数：',center, lastCenter,'\nthis.options',this.options)
        if (lastCenter) {
          if(this.options.lockDragAxis) {
            // 将滚动条锁定到更改最多的位置
            if(Math.abs(center.x - lastCenter.x) > Math.abs(center.y - lastCenter.y)) {
              this.addOffset({
                x: -(center.x - lastCenter.x),
                y: 0,
              });
            }
            else {
              this.addOffset({
                y: -(center.y - lastCenter.y),
                x: 0,
              });
            }
          }
          else {
            this.addOffset({
              y: -(center.y - lastCenter.y),
              x: -(center.x - lastCenter.x),
            });
          }
        }
      },

      /**
             * Calculates the touch center of multiple touches
             * @param touches
             * @return {Object}
             */
      getTouchCenter: function (touches) {
        return this.getVectorAvg(touches);
      },

      /**
             * Calculates the average of multiple vectors (x, y values)
             */
      getVectorAvg: function (vectors) {
        return {
          x: vectors.map(function (v) { return v.x; }).reduce(sum) / vectors.length,
          y: vectors.map(function (v) { return v.y; }).reduce(sum) / vectors.length,
        };
      },

      /**
             * 添加偏移
             * @param offset the offset to add
             * @return return true when the offset change was accepted
             */
      addOffset: function (offset) {
        this.offset = {
          x: this.offset.x + offset.x,
          y: this.offset.y + offset.y,
        };
      },

      sanitize: function () {
        if (this.zoomFactor < this.options.zoomOutFactor) {
          this.zoomOutAnimation();
        } else if (this.isInsaneOffset(this.offset)) {
          this.sanitizeOffsetAnimation();
        }
      },

      /**
             * 检查当前缩放倍数的偏移量是否正确
             * @param offset
             * @return {Boolean}
             */
      isInsaneOffset: function (offset) {
        let sanitizedOffset = this.sanitizeOffset(offset);
        return sanitizedOffset.x !== offset.x ||
                    sanitizedOffset.y !== offset.y;
      },

      /**
             * 创建移动到合理偏移的动画
             */
      sanitizeOffsetAnimation: function () {
        console.log('创建移动到合理偏移的动画');
        let targetOffset = this.sanitizeOffset(this.offset);
        let startOffset = {
          x: this.offset.x,
          y: this.offset.y,
        };
        let updateProgress = (function (progress) {
          this.offset.x = startOffset.x + progress * (targetOffset.x - startOffset.x);
          this.offset.y = startOffset.y + progress * (targetOffset.y - startOffset.y);
          this.update();
        }).bind(this);

        this.animate(
          this.options.animationDuration,
          updateProgress,
          this.swing
        );
      },

      /**
             * 缩放回原始位置,
             * (no offset and zoom factor 1)
             */
      zoomOutAnimation: function () {
        let startZoomFactor = this.zoomFactor;
        let zoomFactor = 1;
        let center = this.getCurrentZoomCenter();
        let updateProgress = (function (progress) {
          this.scaleTo(startZoomFactor + progress * (zoomFactor - startZoomFactor), center);
        }).bind(this);

        this.animate(
          this.options.animationDuration,
          updateProgress,
          this.swing
        );
      },

      /**
             * 更新宽高比
             */
      updateAspectRatio: function () {
        this.setContainerY(this.getContainerX() / this.getAspectRatio());
      },

      /**
             *计算初始缩放系数（以使元素适合容器）
             * @return the initial zoom factor
             */
      getInitialZoomFactor: function () {
        // use .offsetWidth instead of width()
        // because jQuery-width() return the original width but Zepto-width() will calculate width with transform.
        // the same as .height()
        return this.container[0].offsetWidth / this.el[0].offsetWidth;
      },

      /**
             * 计算元素的长宽比
             * @return the aspect ratio
             */
      getAspectRatio: function () {
        return this.el[0].offsetWidth / this.el[0].offsetHeight;
      },

      /**
             * 计算当前偏移量和缩放系数的虚拟缩放中心
             * (used for reverse zoom)
             * @return {Object} the current zoom center
             */
      getCurrentZoomCenter: function () {

        // uses following formula to calculate the zoom center x value
        // offset_left / offset_right = zoomcenter_x / (container_x - zoomcenter_x)
        let length = this.container[0].offsetWidth * this.zoomFactor;
        let offsetLeft  = this.offset.x;
        let offsetRight = length - offsetLeft - this.container[0].offsetWidth;
        let widthOffsetRatio = offsetLeft / offsetRight;
        let centerX = widthOffsetRatio * this.container[0].offsetWidth / (widthOffsetRatio + 1);

        // the same for the zoomcenter y
        let height = this.container[0].offsetHeight * this.zoomFactor;
        let offsetTop  = this.offset.y;
        let offsetBottom = height - offsetTop - this.container[0].offsetHeight;
        let heightOffsetRatio = offsetTop / offsetBottom;
        let centerY = heightOffsetRatio * this.container[0].offsetHeight / (heightOffsetRatio + 1);

        // prevents division by zero
        if (offsetRight === 0) { centerX = this.container[0].offsetWidth; }
        if (offsetBottom === 0) { centerY = this.container[0].offsetHeight; }

        return {
          x: centerX,
          y: centerY,
        };
      },

      canDrag: function () {
        return !isCloseTo(this.zoomFactor, 1);
      },

      /**
             * Returns the touches of an event relative to the container offset
             * @param event
             * @return array touches
             */
      getTouches: function (event) {
        let position = this.container.offset();
        return Array.prototype.slice.call(event.touches).map(function (touch) {
          return {
            x: touch.pageX - position.left,
            y: touch.pageY - position.top,
          };
        });
      },

      /**
             * Animation loop
             * does not support simultaneous animations
             * @param duration
             * @param framefn
             * @param timefn
             * @param callback
             */
      animate: function (duration, framefn, timefn, callback) {
        let startTime = new Date().getTime();
        var renderFrame = (function () {
          if (!this.inAnimation) { return; }
          let frameTime = new Date().getTime() - startTime;
          let progress = frameTime / duration;
          if (frameTime >= duration) {
            framefn(1);
            if (callback) {
              callback();
            }
            this.update();
            this.stopAnimation();
            this.update();
          } else {
            if (timefn) {
              progress = timefn(progress);
            }
            framefn(progress);
            this.update();
            requestAnimationFrame(renderFrame);
          }
        }).bind(this);
        this.inAnimation = true;
        requestAnimationFrame(renderFrame);
      },

      /**
             * Stops the animation
             */
      stopAnimation: function () {
        this.inAnimation = false;
      },

      /**
             * Swing timing function for animations
             * @param p
             * @return {Number}
             */
      swing: function (p) {
        return -Math.cos(p * Math.PI) / 2  + 0.5;
      },

      getContainerX: function () {
        return this.container[0].offsetWidth;
      },

      getContainerY: function () {
        return this.container[0].offsetHeight;
      },

      setContainerY: function (y) {
        return this.container.height(y);
      },

      /**
             * 创建预期的html结构
             */
      setupMarkup: function () {
        this.container = $('<div class="pinch-zoom-container"></div>');
        this.el.before(this.container);
        this.container.append(this.el);

        this.container.css({
          'overflow': 'hidden',
          'position': 'relative',
        });

        // Zepto无法识别“ webkitTransform ..”样式
        this.el.css({
          '-webkit-transform-origin': '0% 0%',
          '-moz-transform-origin': '0% 0%',
          '-ms-transform-origin': '0% 0%',
          '-o-transform-origin': '0% 0%',
          'transform-origin': '0% 0%',
          'position': 'absolute',
        });
      },

      end: function () {
        this.hasInteraction = false;
        this.sanitize();
        this.update();
      },

      /**
             * 绑定所有必需的事件侦听器
             */
      bindEvents: function () {
        detectGestures(this.container.get(0), this);
        // Zepto and jQuery both know about `on`
        $(window).on('resize', this.update.bind(this));
        $(this.el).find('img').on('load', this.update.bind(this));
      },

      /**
             * 根据当前缩放系数和偏移量更新css值
             */
      update: function () {

        if (this.updatePlaned) {
          return;
        }
        this.updatePlaned = true;

        setTimeout((function () {
          this.updatePlaned = false;
          this.updateAspectRatio();

          let zoomFactor = this.getInitialZoomFactor() * this.zoomFactor;
          let offsetX = -this.offset.x / zoomFactor;
          let offsetY = -this.offset.y / zoomFactor;
          let transform3d =   'scale3d('     + zoomFactor + ', '  + zoomFactor + ',1) ' +
                            'translate3d(' + offsetX    + 'px,' + offsetY    + 'px,0px)';
          let transform2d =   'scale('       + zoomFactor + ', '  + zoomFactor + ') ' +
                            'translate('   + offsetX    + 'px,' + offsetY    + 'px)';
          let removeClone = (function () {
            if (this.clone) {
              this.clone.remove();
              delete this.clone;
            }
          }).bind(this);

          let n = "第一行 - this.zoomFactor:" + this.zoomFactor + "<br>" +
                        "|||zoomFactor:" + zoomFactor + "<br>" +
                        "|||transform2d:" + transform2d + "<br>" +
                        "|||transform3d:" + transform3d + "<br>" +
                        "|||this.lastScale:" + this.lastScale + "<br>" +
                        "|||this.lastZoomCenter:" + JSON.stringify(this.lastZoomCenter) + "<br>" +
                        "|||this.offset:" + JSON.stringify(this.offset) + "<br>" +
                        "|||this.nthZoom:" + this.nthZoom + "<br>" +
                        "|||this.zoomFactor:" + this.zoomFactor + "<br>" +
                        "|||this.updatePlaned:" + this.updatePlaned
                    ;
          document.getElementById('logs').innerHTML = n;
          /* console.log(
                        'this.offset:',JSON.stringify(this.offset),
                        '\n',
                        'offsetX:',offsetX,
                        '\n',
                        'offsetY:',offsetY,
                        '\n',
                        'transform2d:',transform2d,
                        '\n',
                        'transform3d:',transform3d,
                        '\n',

                        ''
                    )*/

          // Scale 3d和translate3d更快（至少在iOS上）
          // 但它们也会降低质量
          // PinchZoom在交互过程中使用3d变换
          // 互动后，它会退回到2D转换
          if (!this.options.use2d || this.hasInteraction || this.inAnimation) {
            this.is3d = true;
            removeClone();
            this.el.css({
              '-webkit-transform':  transform3d,
              '-o-transform':       transform2d,
              '-ms-transform':      transform2d,
              '-moz-transform':     transform2d,
              'transform':        transform3d,
            });
          } else {

            // 从3d转换为2d转换时，Webkit会有一些故障。
            // 为避免这种情况，3D变换后的元素的副本会显示在
            // 元素从3d转换为2d转换时的前景
            if (this.is3d) {
              this.clone = this.el.clone();
              this.clone.css('pointer-events', 'none');
              this.clone.appendTo(this.container);
              setTimeout(removeClone, 200);
            }
            this.el.css({
              '-webkit-transform':  transform2d,
              '-o-transform':       transform2d,
              '-ms-transform':      transform2d,
              '-moz-transform':     transform2d,
              'transform':        transform2d,
            });
            this.is3d = false;
          }
        }).bind(this), 0);
      },

      /**
             * Enables event handling for gestures
             */
      enable: function() {
        this.enabled = true;
      },

      /**
             * Disables event handling for gestures
             */
      disable: function() {
        this.enabled = false;
      },
    };

    var detectGestures = function (el, target) {
      let interaction = null;
      let fingers = 0;
      let lastTouchStart = null;
      let startTouches = null;

      let setInteraction = function (newInteraction, event) {
        if (interaction !== newInteraction) {
          // console.log('interaction && !newInteraction: ',interaction,newInteraction,interaction && !newInteraction)
          if (interaction && !newInteraction) {
            switch (interaction) {
            case "zoom":
              target.handleZoomEnd(event);
              break;
            case 'drag':
              target.handleDragEnd(event);
              break;
            }
          }

          switch (newInteraction) {
          case 'zoom':
            target.handleZoomStart(event);
            break;
          case 'drag':
            target.handleDragStart(event);
            break;
          }
        }
        interaction = newInteraction;
      };

      let updateInteraction = function (event) {
        if (fingers === 2) {
          setInteraction('zoom');
        } else if (fingers === 1 && target.canDrag()) {
          setInteraction('drag', event);
        } else {
          setInteraction(null, event);
        }
      };

      let targetTouches = function (touches) {
        return Array.prototype.slice.call(touches).map(function (touch) {
          return {
            x: touch.pageX,
            y: touch.pageY,
          };
        });
      };

      let getDistance = function (a, b) {
        let x, y;
        x = a.x - b.x;
        y = a.y - b.y;
        return Math.sqrt(x * x + y * y);
      };

      let calculateScale = function (startTouches, endTouches) {
        let startDistance = getDistance(startTouches[0], startTouches[1]);
        let endDistance = getDistance(endTouches[0], endTouches[1]);
        return endDistance / startDistance;
      };

      let cancelEvent = function (event) {
        event.stopPropagation();
        event.preventDefault();
      };

      let detectDoubleTap = function (event) {
        let time = (new Date()).getTime();

        if (fingers > 1) {
          lastTouchStart = null;
        }

        if (time - lastTouchStart < 300) {
          cancelEvent(event);

          target.handleDoubleTap(event);
          switch (interaction) {
          case "zoom":
            target.handleZoomEnd(event);
            break;
          case 'drag':
            target.handleDragEnd(event);
            break;
          }
        }

        if (fingers === 1) {
          lastTouchStart = time;
        }
      };
      let firstMove = true;

      el.addEventListener('touchstart', function (event) {
        if(target.enabled) {
          firstMove = true;
          fingers = event.touches.length;
          detectDoubleTap(event);
        }
      });

      el.addEventListener('touchmove', function (event) {
        // console.log('target.enabled: ',target.enabled,'\nfirstMove:',firstMove,'\ninteraction:',interaction)
        if(target.enabled) {
          if (firstMove) {
            /* console.log('单指事件\n',
                            '器使坐标startTouches：',
                            startTouches,
                            '\n',
                            '终点坐标：',
                            targetTouches(event.touches)
                        )*/

            updateInteraction(event);
            if (interaction) {
              cancelEvent(event);
            }
            startTouches = targetTouches(event.touches);
          } else {
            /* console.log('双指事件\n根据起止坐标计算距离变化--',
                            calculateScale(startTouches, targetTouches(event.touches)),
                            '\n',
                            '器使坐标startTouches：',
                            startTouches,
                            '\n',
                            '终点坐标：',
                            targetTouches(event.touches)
                        )*/
            switch (interaction) {
            case 'zoom':
              target.handleZoom(event, calculateScale(startTouches, targetTouches(event.touches)));
              break;
            case 'drag':
              target.handleDrag(event);
              break;
            }
            if (interaction) {
              cancelEvent(event);
              target.update();
            }
          }

          firstMove = false;
        }
      });

      el.addEventListener('touchend', function (event) {
        if(target.enabled) {
          fingers = event.touches.length;
          updateInteraction(event);
        }
      });
    };

    return PinchZoom;
  };

  if (typeof define !== 'undefined' && define.amd) {
    define(['jquery',], function ($) {
      return definePinchZoom($);
    });
  } else {
    window.RTP = window.RTP || {};
    window.RTP.PinchZoom = definePinchZoom(window.$);
  }
}).call(this);